﻿using DTcms.Core.Common.Emum;
using DTcms.Core.Common.Extensions;
using DTcms.Core.Common.Helper;
using DTcms.Core.IServices;
using DTcms.Core.Model.ViewModels;
using System.Collections;
using System.Net;

namespace DTcms.Core.Services
{
    /// <summary>
    /// 文件上传接口实现
    /// </summary>
    public class FileService: IFileService
    {
        private readonly ISysConfigService _configService;
        public FileService(ISysConfigService configService)
        {
            _configService = configService;
        }

        /// <summary>
        /// 通过文件流上传文件方法
        /// </summary>
        /// <param name="byteData">文件字节数组</param>
        /// <param name="fileName">文件名</param>
        /// <param name="isThumbnail">是否生成缩略图</param>
        /// <param name="isWater">是否打水印</param>
        public async Task<FileDto> SaveAsync(byte[] byteData, string fileName, bool isThumbnail, bool isWater)
        {
            //取得站点配置信息
            var sysConfig = await _configService.QueryByTypeAsync(ConfigType.SysConfig);
            if (sysConfig == null)
            {
                throw new ResponseException("系统配置信息不存在");
            }
            var config = JsonHelper.ToJson<SysConfigDto>(sysConfig.JsonData);
            if (config == null)
            {
                throw new ResponseException("系统配置信息格式有误");
            }

            string fileExt = Path.GetExtension(fileName).Trim('.'); //文件扩展名，不含“.”
            string newFileName = Guid.NewGuid() + "." + fileExt; //随机生成新的文件名
            string newThumbnailFileName = "thumb_" + newFileName; //随机生成缩略图文件名

            string upLoadPath = GetUpLoadPath(config); //本地上传目录相对路径
            string fullUpLoadPath = FileHelper.GetWebPath(upLoadPath); //本地上传目录的物理路径
            string newFilePath = upLoadPath + newFileName; //本地上传后的路径
            string newThumbnailPath = upLoadPath + newThumbnailFileName; //本地上传后的缩略图路径

            byte[]? thumbData = null; //缩略图文件流
            FileDto fileDto = new(); //返回的对象

            //检查文件字节数组是否为NULL
            if (byteData == null)
            {
                throw new ResponseException("请选择要上传的文件");
            }
            //检查文件扩展名是否合法
            if (!CheckFileExt(config, fileExt))
            {
                throw new ResponseException($"不允许上传{fileExt}类型的文件");
            }
            //检查文件大小是否合法
            if (!CheckFileSize(config, fileExt, byteData.Length))
            {
                throw new ResponseException($"文件超过限制的大小");
            }

            //如果是图片，检查图片是否超出最大尺寸，是则裁剪
            if (IsImage(fileExt) && (config.ImgMaxHeight > 0 || config.ImgMaxWidth > 0))
            {
                byteData = ImageHelper.MakeThumbnail(byteData, fileExt, config.ImgMaxWidth, config.ImgMaxHeight);
            }
            //如果是图片，检查是否需要生成缩略图，是则生成
            if (IsImage(fileExt) && isThumbnail && config.ThumbnailWidth > 0 && config.ThumbnailHeight > 0)
            {
                thumbData = ImageHelper.MakeThumbnail(byteData, fileExt, config.ThumbnailWidth, config.ThumbnailHeight, config.ThumbnailMode);
            }
            else
            {
                newThumbnailPath = newFilePath; //不生成缩略图则返回原图
            }
            //如果是图片，检查是否需要打水印
            if (IsWaterMark(config, fileExt) && isWater)
            {
                switch (config.WatermarkType)
                {
                    case 1:
                        byteData = ImageHelper.LetterWatermark(byteData, fileExt, config.WatermarkText, config.WatermarkPosition,
                            config.WatermarkImgQuality, config.WatermarkFont, config.WatermarkFontSize);
                        break;
                    case 2:
                        byteData = ImageHelper.ImageWatermark(byteData, fileExt,
                            FileHelper.GetWebPath(config.WatermarkPic), config.WatermarkPosition,
                            config.WatermarkImgQuality, config.WatermarkTransparency);
                        break;
                }
            }

            //七牛云存储
            if (config.FileServer.Equals("qiniu"))
            {
                if (!config.KodoBucket.IsNotNullOrEmpty() || !config.KodoDomain.IsNotNullOrEmpty() || !config.KodoAccessKey.IsNotNullOrEmpty() || !config.KodoSecretKey.IsNotNullOrEmpty())
                {
                    throw new ResponseException($"七牛云存储参数未设置");
                }
                //保存主文件
                QiniuHelper.UploadFile(byteData, config.KodoAccessKey, config.KodoSecretKey, config.KodoBucket, newFilePath.TrimStart('/'));
                //保存缩略图文件
                if (thumbData != null)
                {
                    QiniuHelper.UploadFile(thumbData, config.KodoAccessKey, config.KodoSecretKey, config.KodoBucket, newThumbnailPath.TrimStart('/'));
                }
                //返回上传路径
                fileDto.FilePath = config.KodoDomain + newFilePath;
                if (isWater)
                {
                    fileDto.ThumbPath = new List<string>() { config.KodoDomain + newThumbnailPath }; //如果有缩略图
                }
            }
            //阿里云存储
            else if (config.FileServer.Equals("aliyun"))
            {
                if (!config.OssBucket.IsNotNullOrEmpty() || !config.OssDomain.IsNotNullOrEmpty() || !config.OssEndpoint.IsNotNullOrEmpty() || !config.OssAccessKey.IsNotNullOrEmpty() || !config.OssSecretKey.IsNotNullOrEmpty())
                {
                    throw new ResponseException($"阿里云存储参数未设置");
                }
                //保存主文件
                OssHelper.UploadFile(byteData, config.OssEndpoint, config.OssAccessKey, config.OssSecretKey, config.OssBucket, newFilePath.TrimStart('/'));
                //保存缩略图文件
                if (thumbData != null)
                {
                    OssHelper.UploadFile(thumbData, config.OssEndpoint, config.OssAccessKey, config.OssSecretKey, config.OssBucket, newThumbnailPath.TrimStart('/'));
                }
                //返回上传路径
                fileDto.FilePath = config.OssDomain + newFilePath;
                if (isWater)
                {
                    fileDto.ThumbPath = new List<string>() { config.OssDomain + newThumbnailPath }; //如果有缩略图
                }
            }
            //本地存储
            else
            {
                //检查本地上传的物理路径是否存在，不存在则创建
                if (!Directory.Exists(fullUpLoadPath))
                {
                    Directory.CreateDirectory(fullUpLoadPath);
                }
                //保存主文件
                FileHelper.SaveFile(byteData, fullUpLoadPath + newFileName);
                //保存缩略图文件
                if (thumbData != null)
                {
                    FileHelper.SaveFile(thumbData, fullUpLoadPath + newThumbnailFileName);
                }
                //返回上传路径
                fileDto.FilePath = newFilePath;
                if (isWater)
                {
                    fileDto.ThumbPath = new List<string>() { newThumbnailPath }; //如果有缩略图
                }
            }
            //返回成功信息
            fileDto.FileName = fileName;
            fileDto.FileSize = byteData.Length;
            fileDto.FileExt = fileExt;
            return fileDto;
        }

        /// <summary>
        /// 裁剪图片并保存
        /// </summary>
        public async Task<FileDto> CropAsync(string fileUri, int maxWidth, int maxHeight, int cropWidth, int cropHeight, int X, int Y)
        {
            FileDto fileDto = new FileDto();//返回信息
            string fileExt = Path.GetExtension(fileUri).Trim('.'); //文件扩展名，不含“.”
            if (string.IsNullOrEmpty(fileExt) || !IsImage(fileExt))
            {
                throw new ResponseException($"该文件不是图片");
            }

            byte[]? byteData = null;
            //判断是否远程文件
            if (fileUri.ToLower().StartsWith("http://") || fileUri.ToLower().StartsWith("https://"))
            {
                using HttpClient client = new();
                byteData = await client.GetByteArrayAsync(fileUri);
            }
            else //本地源文件
            {
                string fullName = FileHelper.GetWebPath(fileUri);
                if (File.Exists(fullName))
                {
                    FileStream fs = File.OpenRead(fullName);
                    BinaryReader br = new BinaryReader(fs);
                    br.BaseStream.Seek(0, SeekOrigin.Begin);
                    byteData = br.ReadBytes((int)br.BaseStream.Length);
                    fs.Close();
                }
            }
            if (byteData == null)
            {
                throw new ResponseException($"无法获取原图片");
            }
            //裁剪后得到文件流
            byteData = ImageHelper.MakeThumbnail(byteData, fileExt, maxWidth, maxHeight, cropWidth, cropHeight, X, Y);
            fileDto = await SaveAsync(byteData, fileUri, false, false);
            await DeleteAsync(fileUri); //删除原图
            return fileDto;
        }

        /// <summary>
        /// 保存远程文件到本地
        /// </summary>
        /// <param name="sourceUri">URI地址</param>
        /// <returns>上传后的路径</returns>
        public async Task<FileDto> RemoteAsync(string sourceUri)
        {
            FileDto fileDto = new();//返回消息

            if (!IsExternalIPAddress(sourceUri))
            {
                throw new ResponseException($"INVALID_URL");
            }
            using var response = await new HttpClient().GetAsync(sourceUri);
            if (response == null)
            {
                throw new ResponseException($"抓取文件错误");
            }
            if (response.StatusCode != HttpStatusCode.OK)
            {
                throw new ResponseException($"Url returns{response?.StatusCode}, {response?.RequestMessage}");
            }
            if (response.Content.Headers.ContentType?.MediaType?.IndexOf("image") == -1)
            {
                throw new ResponseException($"Url is not an image");
            }
            byte[] byteData = await response.Content.ReadAsByteArrayAsync();
            return await SaveAsync(byteData, sourceUri, false, false);
        }

        /// <summary>
        /// 删除文件
        /// </summary>
        public async Task<bool> DeleteAsync(string fileUri)
        {
            //取得站点配置信息
            var sysConfig = await _configService.QueryByTypeAsync(ConfigType.SysConfig);
            if (sysConfig == null)
            {
                throw new ResponseException("站点配置信息不存在");
            }
            var config = JsonHelper.ToJson<SysConfigDto>(sysConfig.JsonData);
            if (config == null)
            {
                throw new ResponseException("站点配置信息格式有误");
            }
            bool result = false;

            //七牛云存储
            if (config.FileServer.Equals("qiniu"))
            {
                if (!config.KodoBucket.IsNotNullOrEmpty() || !config.KodoDomain.IsNotNullOrEmpty() || !config.KodoAccessKey.IsNotNullOrEmpty() || !config.KodoSecretKey.IsNotNullOrEmpty())
                {
                    throw new ResponseException($"七牛云存储参数未设置");
                }
                //替换成相对路径
                var filePath = fileUri.Replace(config.KodoDomain ?? String.Empty, string.Empty);
                result = QiniuHelper.DeleteFile(config.KodoAccessKey, config.KodoSecretKey, config.KodoBucket, filePath.TrimStart('/'));
            }
            //阿里云存储
            else if (config.FileServer.Equals("aliyun"))
            {
                if (!config.OssBucket.IsNotNullOrEmpty() || !config.OssDomain.IsNotNullOrEmpty() || !config.OssEndpoint.IsNotNullOrEmpty() || !config.OssAccessKey.IsNotNullOrEmpty() || !config.OssSecretKey.IsNotNullOrEmpty())
                {
                    throw new ResponseException($"阿里云存储参数未设置");
                }
                //替换成相对路径
                var filePath = fileUri.Replace(config.OssDomain ?? String.Empty, string.Empty);
                result = QiniuHelper.DeleteFile(config.KodoAccessKey, config.KodoSecretKey, config.KodoBucket, filePath.TrimStart('/'));
            }
            //本地存储
            else
            {
                result = FileHelper.DeleteFile(FileHelper.GetWebPath(fileUri));
            }
            return false;
        }

        #region 辅助私有方法
        /// <summary>
        /// 返回上传目录相对路径
        /// </summary>
        private string GetUpLoadPath(SysConfigDto config)
        {
            string path = $"/{config.FilePath}/";
            switch (config.FileSave)
            {
                case 1: //按年月日每天一个文件夹
                    path += DateTime.Now.ToString("yyyyMMdd");
                    break;
                default: //按年月/日存入不同的文件夹
                    path += DateTime.Now.ToString("yyyyMM") + "/" + DateTime.Now.ToString("dd");
                    break;
            }
            return path + "/";
        }

        /// <summary>
        /// 是否需要打水印
        /// </summary>
        private bool IsWaterMark(SysConfigDto config, string fileExt)
        {
            //判断是否开启水印
            if (config.WatermarkType > 0)
            {
                //判断是否可以打水印的图片类型
                ArrayList al = new ArrayList();
                al.Add("bmp");
                al.Add("jpeg");
                al.Add("jpg");
                al.Add("png");
                if (al.Contains(fileExt.ToLower()))
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// 是否为图片文件
        /// </summary>
        /// <param name="fileExt">文件扩展名，不含“.”</param>
        private bool IsImage(string fileExt)
        {
            ArrayList al = new ArrayList();
            al.Add("bmp");
            al.Add("jpeg");
            al.Add("jpg");
            al.Add("gif");
            al.Add("png");
            if (al.Contains(fileExt.ToLower()))
            {
                return true;
            }
            return false;
        }

        /// <summary>
        /// 检查是否为合法的上传文件
        /// </summary>
        private bool CheckFileExt(SysConfigDto config, string fileExt)
        {
            //检查危险文件
            string[] excExt = { "asp", "aspx", "ashx", "asa", "asmx", "asax", "php", "jsp", "htm", "html" };
            for (int i = 0; i < excExt.Length; i++)
            {
                if (excExt[i].ToLower() == fileExt.ToLower())
                {
                    return false;
                }
            }
            //检查合法文件
            string[] allowExt = (config.FileExtension + "," + config.VideoExtension).Split(',');
            for (int i = 0; i < allowExt.Length; i++)
            {
                if (allowExt[i].ToLower() == fileExt.ToLower())
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// 检查文件大小是否合法
        /// </summary>
        /// <param name="fileExt">文件扩展名，不含“.”</param>
        /// <param name="fileSize">文件大小(B)</param>
        private bool CheckFileSize(SysConfigDto config, string fileExt, int fileSize)
        {
            if (config.VideoExtension == null)
            {
                return false;
            }
            //将视频扩展名转换成ArrayList
            ArrayList lsVideoExt = new ArrayList(config.VideoExtension.ToLower().Split(','));
            //判断是否为图片文件
            if (IsImage(fileExt))
            {
                if (config.ImgSize > 0 && fileSize > config.ImgSize * 1024)
                {
                    return false;
                }
            }
            else if (lsVideoExt.Contains(fileExt.ToLower()))
            {
                if (config.VideoSize > 0 && fileSize > config.VideoSize * 1024)
                {
                    return false;
                }
            }
            else
            {
                if (config.AttachSize > 0 && fileSize > config.AttachSize * 1024)
                {
                    return false;
                }
            }
            return true;
        }

        /// <summary>
        /// 检查文件地址是否文件服务器地址
        /// </summary>
        /// <param name="url">文件地址</param>
        private bool IsExternalIPAddress(string url)
        {
            var uri = new Uri(url);
            switch (uri.HostNameType)
            {
                case UriHostNameType.Dns:
                    var ipHostEntry = Dns.GetHostEntry(uri.DnsSafeHost);
                    foreach (IPAddress ipAddress in ipHostEntry.AddressList)
                    {
                        byte[] ipBytes = ipAddress.GetAddressBytes();
                        if (ipAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
                        {
                            if (!IsPrivateIP(ipAddress))
                            {
                                return true;
                            }
                        }
                    }
                    break;

                case UriHostNameType.IPv4:
                    return !IsPrivateIP(IPAddress.Parse(uri.DnsSafeHost));
            }
            return false;
        }

        /// <summary>
        /// 检查IP地址是否本地服务器地址
        /// </summary>
        /// <param name="myIPAddress">IP地址</param>
        private bool IsPrivateIP(IPAddress myIPAddress)
        {
            if (IPAddress.IsLoopback(myIPAddress)) return true;
            if (myIPAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
            {
                byte[] ipBytes = myIPAddress.GetAddressBytes();
                // 10.0.0.0/24 
                if (ipBytes[0] == 10)
                {
                    return true;
                }
                // 172.16.0.0/16
                else if (ipBytes[0] == 172 && ipBytes[1] == 16)
                {
                    return true;
                }
                // 192.168.0.0/16
                else if (ipBytes[0] == 192 && ipBytes[1] == 168)
                {
                    return true;
                }
                // 169.254.0.0/16
                else if (ipBytes[0] == 169 && ipBytes[1] == 254)
                {
                    return true;
                }
            }
            return false;
        }
        #endregion
    }
}
